/*
	Copyright 2012 J. Gohlke
	Licensed under the Apache License, Version 2.0 (the
	"License"); you may not use this file except in
	compliance with the License. You may obtain a copy
	of the License at

	http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in
	writing, software distributed under the License is
	distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
	OR CONDITIONS OF ANY KIND, either express or
	implied. See the License for the specific language
	governing permissions and limitations under the
	License
 */
package com.gohlke.lwo.io;

import com.gohlke.jme3.LightwaveObjectLoader;
import com.gohlke.lwo.*;
import com.gohlke.lwo.clips.*;
import com.gohlke.lwo.params.FloatParam;
import com.gohlke.lwo.params.Vector3dParam;
import com.gohlke.lwo.textures.*;
import com.gohlke.lwo.types.ColorSpace;
import com.gohlke.lwo.types.EnvelopeKey;
import com.gohlke.lwo.types.EnvelopeKeyShape;
import com.gohlke.lwo.types.Plugin;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;

/**
 *
 * @author David Wise
 * @author Jennifer Gohlke
 */
public class LightwaveObjectReader extends LightwaveObjectInputStream
{
	private static final Logger logger = Logger.getLogger( LightwaveObjectLoader.class.getName() );
	
    private final String fileTag = "FORM";
    private final String versionTag = "LWO2";
    
    private LightwaveObjectLayer currentLayer;
    protected LightwaveObject lightwaveObject = new LightwaveObject();

	public LightwaveObjectReader( InputStream in ) throws IOException, FormatException
	{
        super( in );

		if( !fileTag.equals( this.readTag() ) )
			throw new FormatException( "Invalid file. Not LWO" );

		int len = super.readInt();
		if( len != this.available() )
			throw new FormatException( "Invalid format. Wrong size." );

		if( !versionTag.equals( this.readTag() ) )
			throw new FormatException( "Invalid format version. Not LWO2" );

		this.currentLayer = lightwaveObject.baseLayer;

		while( this.available() > 0 )
		{
			String chunkId = this.readTag();

			if( chunkId.equals( "" ) )
				break;

			if( this.available() < 4 )
				break;

			int chunkLen = this.readInt();
			byte[] buffer = new byte[ chunkLen ];
			int read = this.read( buffer, 0, chunkLen );
			ChunkStream chunk = new ChunkStream( chunkId, buffer );

			parseChunk( chunk );

			// even padding
			if( chunkLen % 2 != 0 )
				super.skipBytes( 1 );
		}

		logger.info( "Parsing finished." );

	}

	public LightwaveObject getObject()
	{
		return lightwaveObject;
	}
    
    private void parseChunk(ChunkStream chunk) throws IOException {
        ChunkId id = null;
        try {
            id = ChunkId.valueOf(chunk.chunkId);
        } catch (IllegalArgumentException ex){
            // indicates that a parser error has ocurred.
            // or it defines an unknown chunk
            logger.warning("Not defined chunk?: " + chunk.chunkId);
        }
        chunk.setPositionMark();
        int chunkLen = chunk.length;
        switch (id)
        {
            case LAYR:
                lightwaveObject.layers.add(currentLayer = this.readLayer(chunk)) ;
                break;
            case PNTS:
                int n = chunkLen / 12;
                for (int i = 0; i < n; i++)
                    currentLayer.vertices.add(chunk.readVector3f());
                break;
            case POLS:
                this.readPolygons(chunk);
                break;
            case VMAP:
            case VMAD:
                currentLayer.vertexMapping.add(
                        this.readVertexMapping(chunk, id == ChunkId.VMAD));
                break;
            case VMPA:
                // TODO: VMPA ??? where to place it?
                LightwaveObjectVertexMapParameter par = new LightwaveObjectVertexMapParameter();
                par.type = chunk.readInt();
                par.sketchColor = chunk.readInt();

                break;
            case SURF:
                lightwaveObject.surfaces.add(this.readSurface(chunk));
                break;
            case BBOX:
                currentLayer.boundingBox = chunk.readBoundingBox();
                break;
            case TAGS:
                while (chunk.getReadFromMark() < chunkLen)
                    lightwaveObject.tags.add(chunk.readString());
                break;
            case ICON:
                currentLayer.icon = chunk.readIcon(chunkLen);
                break;
            case DESC:
                currentLayer.description = chunk.readString();
                break;
            case TEXT:
                currentLayer.comments = chunk.readString();
                break;
            case PTAG:
                this.readPolygonTag(chunk);
                break;
            case ENVL:
                lightwaveObject.envelopes.add(this.readEnvelope(chunk));
                break;
            case CLIP:
                lightwaveObject.clips.add(this.readClip(chunk));
                break;
            default:
                // if unknow or unnecessary, just skip it
                logger.info( "Not implemented chunk: " + id.toString() );
                break;
        }

    }
    
    private LightwaveObjectClip readClip(ChunkStream chunk) throws IOException {
        long clipIndex = chunk.readInt();
        String tag = chunk.readTag();
        LightwaveObjectClip clip = new LightwaveObjectClip(clipIndex, ClipType.valueOf(tag));
        int chunkLen = chunk.readUnsignedShort();
        chunk.setPositionMark();
        if (clip.type.equals(ClipType.STIL))
            clip.source = new ClipStill(chunk.readString());
        else if (clip.type.equals(ClipType.ISEQ))
            clip.source = new ClipSequence(chunk.read(), chunk.read(),
                    chunk.readShort(), chunk.readUnsignedShort(),
                    chunk.readShort(), chunk.readShort(),
                    chunk.readString(), chunk.readString());
        else if (clip.type.equals(ClipType.ANIM))
            clip.source = new ClipAnim(chunk.readString(), chunk.readString(), 
                    chunk.readUnsignedShort(),
                    chunk.readBytes(chunkLen - chunk.getReadFromMark()));
        else if (clip.type.equals(ClipType.XREF))
            clip.source = new ClipReference(chunk.readInt(),
                        chunk.readString());
        else if (clip.type.equals(ClipType.STCC))
            clip.source = new ClipCycle(chunk.readShort(),
                        chunk.readShort(), chunk.readString());
        else {
            logger.warning("Not defined clip type");
            return null;
        }
        
        while (chunk.available() > 0) {
            String clipTag = chunk.readTag();
            int len = chunk.readUnsignedShort();
            chunk.setPositionMark();
            
            LightwaveObjectClipChunkId id = LightwaveObjectClipChunkId.valueOf(clipTag);
            switch (id){
                case TIME:
                    clip.startTime = chunk.readFloat();
                    clip.duration = chunk.readFloat();
                    clip.frameRate = chunk.readFloat();
                    break;
                case CLRS:
                    clip.colorSpaceRGB = new ColorSpace(
                            chunk.readUnsignedShort(),
                            chunk.readUnsignedShort(),
                            chunk.readString());
                    break;
                case CLRA:
                    clip.colorSpaceAlpha = new ColorSpace(
                            chunk.readUnsignedShort(),
                            chunk.readUnsignedShort(),
                            chunk.readString());
                    break;
                case FILT:
                    clip.currentImageFiltering = chunk.readUnsignedShort();
                    break;
                case DITH:
                    clip.currentImageDithering = chunk.readUnsignedShort();
                    break;
                case CONT:
                    clip.contrast = chunk.readFloatParam();
                    break;
                case BRIT:
                    clip.brightness = chunk.readFloatParam();
                    break;
                case SATR:
                    clip.saturation = chunk.readFloatParam();
                    break;
                case HUE:
                    clip.hue = chunk.readFloatParam();
                    break;
                case GAMM:
                    clip.gamma = chunk.readFloatParam();
                    break;
                case NEGA:
                    clip.negative = (chunk.readUnsignedShort() != 0);
                    break;
                case IFLT:
                case PFLT:
                    Plugin plugin = new Plugin();
                    plugin.name = chunk.readString();
                    plugin.flags = chunk.readUnsignedShort();
                    plugin.data = chunk.readBytes(len - chunk.getReadFromMark());
                    
                    if (id.equals(LightwaveObjectClipChunkId.IFLT))
                        clip.imageFilters.add(plugin);
                    else
                        clip.pixelFilters.add(plugin);
                    break;
            }
            
            int read = chunk.getReadFromMark();
            if (read < len)
                chunk.skipBytes(len - read);
        }
         
        return clip;
    }
    
    private LightwaveObjectLayer readLayer(ChunkStream chunk) throws IOException {
        chunk.setPositionMark();
        
        LightwaveObjectLayer layer = new LightwaveObjectLayer();
        layer.index = chunk.readUnsignedShort();
        layer.flags = chunk.readUnsignedShort();
        layer.pivotPoint = chunk.readVector3f();
        layer.name = chunk.readString();
        
        layer.parentIndex = 
                (chunk.getReadFromMark() == chunk.length) ?
                -1 : chunk.readShort();
        return layer;
    }
    
    private void readPolygons(ChunkStream chunk) throws IOException {
        chunk.setPositionMark();
        
        String polyType = chunk.readTag();
        
        // count vertices
        int polyCount = 0;
        while (chunk.getReadFromMark() < chunk.length) {
            int nv = chunk.readUnsignedShort();
            nv &= 0x03ff;
            polyCount++;
            for (int i = 0; i < nv; i++ )
                chunk.readVX();
        }
        
        chunk.reset();
        polyType = chunk.readTag();
        
        for (int  i = 0; i < polyCount; i++ ) {
            int nv = chunk.readUnsignedShort();
            LightwaveObjectPolygon poly = new LightwaveObjectPolygon();
            poly.type = LightwaveObjectPolygonType.valueOf(polyType);
            poly.flags = nv & 0xFC00;
            nv &= 0x03FF;
            poly.numVertices = nv;

            poly.vertexIndices = new int[poly.numVertices];
            for (int j = 0; j < nv; j++ )
                poly.vertexIndices[ j ] = chunk.readVX();
            
            currentLayer.polygons.add(poly);
       }
        
    }
    
    private void readPolygonTag(ChunkStream chunk) throws IOException {
        chunk.setPositionMark();
        String typeTag = chunk.readTag();
        while (chunk.getReadFromMark() < chunk.length)
        {
            int poly = chunk.readVX();
            int ptag = chunk.readUnsignedShort();
            
            if (typeTag.equals("SURF"))
                currentLayer.polygons.get(poly).surfaceTag = ptag;
            else if (typeTag.equals("PART"))
                currentLayer.polygons.get(poly).partTag = ptag;
            else if (typeTag.equals("SMGP"))
                currentLayer.polygons.get(poly).smoothGroupTag = ptag;
        }
    
    }
    
    private LightwaveObjectVertexMapping readVertexMapping(ChunkStream chunk,
            Boolean discontinuous) throws IOException {
        chunk.setPositionMark();
                
        LightwaveObjectVertexMapping vmap = new LightwaveObjectVertexMapping();
        vmap.type = chunk.readTag();
        vmap.dimension = chunk.readUnsignedShort();
        vmap.name = chunk.readString();
        
        while (chunk.getReadFromMark() < chunk.length)
        {
            LightwaveObjectVertexMapData mapData = new LightwaveObjectVertexMapData();
            mapData.vertexIndex = chunk.readVX();
            
            if (discontinuous)
                mapData.polygonIndex = chunk.readVX();
            
            mapData.values = new float[vmap.dimension];
            for (int i = 0; i < vmap.dimension; i++)
                mapData.values[i] = chunk.readFloat();
            
            vmap.data.add(mapData);
        }
        return vmap;
    }
    
    private LightwaveObjectEnvelope readEnvelope(ChunkStream chunk) throws IOException{
        LightwaveObjectEnvelope envelope = new LightwaveObjectEnvelope();
        chunk.setPositionMark();
        envelope.index = chunk.readVX();
        
        EnvelopeKey key = null;
        while (chunk.getReadFromMark() < chunk.length) {
            String subChunkTag = chunk.readTag();
            int subChunkLen = chunk.readUnsignedShort();
            byte[] buffer = new byte[subChunkLen];
            int read = chunk.read(buffer, 0, subChunkLen);
            EnvelopeChunkId id = EnvelopeChunkId.valueOf( subChunkTag );
            ChunkStream subChunk = new ChunkStream(subChunkTag, buffer);
            subChunk.setPositionMark();
            switch (id) {
                case TYPE:
                    // ONLY USEFUL for the LightWave interface
                    int userFormat = subChunk.read();
                    int types = subChunk.read();
                    break;
                case PRE:
                    envelope.behavior[0] = subChunk.readUnsignedShort();
                    break;
                case POST:
                    envelope.behavior[1] = subChunk.readUnsignedShort();
                    break;
                case KEY:
                    envelope.keys.add(key = new EnvelopeKey(
                            subChunk.readFloat(),subChunk.readFloat()));
                    break;
                case SPAN:
                    key.shape = EnvelopeKeyShape.valueOf( subChunk.readTag() );
                    
                    // read key parameters
                    int n = (subChunkLen-4)/4;
                    if (n > 4){
                        // TOCHECK: Wrong file format?
                        n = 4;
                    }
                    float[] f = new float[n];
                    for (int i = 0; i < n; i++)
                        f[i] = subChunk.readFloat();

                    switch ( key.shape ) 
                    {
                        case TCB:
                            key.tension = f[0];
                            key.continuity = f[1];
                            key.bias = f[2];
                            break;
                        case BEZI:
                        case HERM:
                        case BEZ2:
                            key.param = f;
                            break;
                    }
                    break;
                case CHAN:
                    Plugin plugin = new Plugin();
                    plugin.name = subChunk.readString();
                    plugin.flags = subChunk.readUnsignedShort();
                    int dataSize = subChunkLen - subChunk.getReadFromMark();
                    plugin.data = new byte[dataSize];
                    subChunk.read(plugin.data, 0, dataSize);
                    envelope.plugins.add(plugin);
                    break;
                case NAME:
                    envelope.name = subChunk.readString();
                    break;
            }
            // chunk.skipBytes(subChunkLen);
        }
        return envelope;
    }
    
    private LightwaveObjectSurface readSurface(ChunkStream chunk) throws IOException {
        chunk.setPositionMark();
        LightwaveObjectSurface surf = new LightwaveObjectSurface();
        surf.name = chunk.readString();
        surf.source = chunk.readString();
        
        while (chunk.getReadFromMark() < chunk.length){
            String chunkTag = chunk.readTag();
            int chunkLen = chunk.readUnsignedShort();
            LightwaveObjectSurfaceChunkId id = LightwaveObjectSurfaceChunkId.VCOL;
            try {
                id = LightwaveObjectSurfaceChunkId.valueOf(chunkTag);
            } catch (Exception ex) {
                logger.info( "***> " + chunkTag + " not defined" );
                chunk.skipBytes(chunkLen);
            }
            
            switch (id)
            {
                case COLR:
                    surf.color.rgb = chunk.readColor();
                    surf.color.envelopeIndex = chunk.readVX();
                    break;
                case DIFF:
                    surf.difusse = chunk.readFloatParam();
                    break;
                case LUMI:
                    surf.luminosity = chunk.readFloatParam();
                    break;
                case SPEC:
                    surf.specularity = chunk.readFloatParam();
                    break;
                case GLOS:
                    surf.glossiness = chunk.readFloatParam();
                    break;
                case REFL:
                    surf.reflection.value = chunk.readFloatParam();
                    break;
                case RFOP:
                    surf.reflection.options = chunk.readUnsignedShort();
                    break;
                case RIMG:
                    surf.reflection.cIndex = chunk.readVX();
                    break;
                case RSAN:
                    surf.reflection.seamAngle = chunk.readFloat();
                    break;
                case TRAN:
                    surf.transparency.value = chunk.readFloatParam();
                    break;
                case TROP:
                    surf.transparency.options = chunk.readUnsignedShort();
                    break;
                case TIMG:
                    surf.transparency.cIndex = chunk.readVX();
                    break;
                case RIND:
                    surf.eta = chunk.readFloatParam();
                    break;
                case TRNL:
                    surf.translucency = chunk.readFloatParam();
                    break;
                case BUMP:
                    surf.bump = chunk.readFloatParam();
                    break;
                case SMAN:
                    surf.smooth = chunk.readFloat();
                    break;
                case SIDE:
                    surf.sideFlags = chunk.readUnsignedShort();
                    break;
                case CLRH:
                    surf.color_hilite = chunk.readFloatParam();
                    break;
                case CLRF:
                    surf.color_filter = chunk.readFloatParam();
                    break;
                case ADTR:
                    surf.add_trans = chunk.readFloatParam();
                    break;
                case SHRP:
                    surf.dif_sharp = chunk.readFloatParam();
                    break;
                case GVAL:
                    surf.glow = chunk.readFloatParam();
                    break;
                case LINE:
                    surf.line.enabled = 1;
                    if (chunkLen >= 2)
                        surf.line.flags = chunk.readUnsignedShort();
                    if (chunkLen >= 4)
                        surf.line.size = new FloatParam(chunk.readFloat());
                    if (chunkLen >= 6)
                        surf.line.size.envelopeIndex = chunk.readVX();
                    break;
                case ALPH:
                    surf.alphaMode = chunk.readUnsignedShort();
                    surf.alpha = chunk.readFloat();
                    break;
                case AVAL:
                    surf.alpha = chunk.readFloat();
                    break;
                case BLOK:
                    byte[] buffer = new byte[chunkLen];
                    chunk.read(buffer, 0, chunkLen);
                    this.readBlock(surf, buffer);
                    break;
                default:
                    this.skipBytes(chunkLen);
            }
        }
        
        return surf;
    }

    private void readBlock(LightwaveObjectSurface surf, byte[] buffer) throws IOException {
        ChunkStream chunk = new ChunkStream("BLOK", buffer);
        while (chunk.available() > 0) {
            String blokType = chunk.readTag();
            int blockLen = chunk.readUnsignedShort();
            byte[] b = new byte[blockLen];
            chunk.read(b, 0, blockLen);
            ChunkStream blockStream = new ChunkStream(blokType, b);
            if (blokType.equals("IMAP") ||
                blokType.equals("PROC") ||
                blokType.equals("GRAD")) {
                
                Texture tex = readTexture(blockStream, BlockType.valueOf(blokType));
                if (blokType.equals("IMAP"))
                    tex.map = this.readImageMap(chunk);
                else if (blokType.equals("PROC"))
                    tex.map = this.readProcMap(chunk);
                else
                    tex.map = this.readGradientMap(chunk);
                
                this.addTexture(tex, surf);
                
            } else if (blokType.equals("SHDR")) {
                surf.shaders.add(readShaderFunction(chunk, readShader(blockStream)));
            }
        }
    }
    
    private void addTexture(Texture tex, LightwaveObjectSurface surf) {
        TextureChannel channel = TextureChannel.valueOf(tex.channel);
        if (channel.equals(TextureChannel.COLR))
            surf.color.textures.add(tex);
        else if (channel.equals(TextureChannel.LUMI))
            surf.luminosity.textures.add(tex);
        else if (channel.equals(TextureChannel.DIFF))
            surf.difusse.textures.add(tex);
        else if (channel.equals(TextureChannel.SPEC))
            surf.specularity.textures.add(tex);
        else if (channel.equals(TextureChannel.GLOS))
            surf.glossiness.textures.add(tex);
        else if (channel.equals(TextureChannel.REFL))
            surf.reflection.value.textures.add(tex);
        else if (channel.equals(TextureChannel.TRAN))
            surf.transparency.value.textures.add(tex);
        else if (channel.equals(TextureChannel.RIND))
            surf.eta.textures.add(tex);
        else if (channel.equals(TextureChannel.TRNL))
            surf.translucency.textures.add(tex);
        else if (channel.equals(TextureChannel.BUMP))
            surf.bump.textures.add(tex);
    }
    
    private Plugin readShaderFunction(ChunkStream chunk,
            Plugin shader) throws IOException {
        while (chunk.available() > 0) {
            String headerTag = chunk.readTag();
            int headerSize = chunk.readUnsignedShort();
            chunk.setPositionMark();
            if (headerTag.equals("FUNC")) {
                shader.name = chunk.readString();
                shader.data = chunk.readBytes(
                        headerSize - chunk.getReadFromMark());
            }
            
            int read = chunk.getReadFromMark();
            if (read < headerSize)
                chunk.skipBytes(headerSize - read);
        }
        return shader;
    }
    
    private Plugin readShader(ChunkStream chunk) throws IOException {
        Plugin shader = new Plugin();
        shader.ordinalString = chunk.readString();
        while (chunk.available() > 0) {
            String headerTag = chunk.readTag();
            int headerSize = chunk.readUnsignedShort();
            chunk.setPositionMark();
            if (headerTag.equals("ENAB"))
                shader.flags = chunk.readUnsignedShort();
            else if (headerTag.equals("FUNC")) {
                shader.name = chunk.readString();
                shader.data = chunk.readBytes(
                        headerSize - chunk.getReadFromMark());
            }
            
            int read = chunk.getReadFromMark();
            if (read < headerSize)
                chunk.skipBytes(headerSize - read);
        }
        return shader;
    }
    
    private Texture readTexture(ChunkStream chunk,
            BlockType type) throws IOException {
        Texture texture = new Texture();
        texture.type = type;
        // read ordinal string
        texture.ord = chunk.readString();
        
        while (chunk.available() > 0) {
            String headerTag = chunk.readTag();
            int headerSize = chunk.readUnsignedShort();
            int _offset = chunk.available();
            TextureHeaderType id = TextureHeaderType.valueOf(headerTag);
            switch (id) {
                case CHAN:
                    texture.channel = chunk.readTag();
                    break;
                case OPAC:
                    texture.opacityType = chunk.readUnsignedShort();
                    texture.opacity = chunk.readFloatParam();
                    break;
                case ENAB:
                    texture.enabled = (chunk.readUnsignedShort() != 0);
                    break;
                case NEGA:
                    texture.negative = (chunk.readUnsignedShort() != 0);
                    break;
                case AXIS:
                    texture.axis = chunk.readUnsignedShort();
                    break;
            }
            int read = _offset - chunk.available();
            if (read < headerSize)
                chunk.skipBytes(headerSize - read);
        }
        return texture;
    }
    
        
    private ImageMap readImageMap(ChunkStream chunk) throws IOException
    {
        ImageMap map = new ImageMap();
        while (chunk.available() > 0) {
            String imapTag = chunk.readTag();
            int imapLen = chunk.readUnsignedShort();
            chunk.setPositionMark();
            if (imapTag.equals("TMAP")){
                byte[] buffer = new byte[imapLen];
                chunk.read(buffer, 0, imapLen);
                this.readTextureMap(map, buffer);
            } else if (imapTag.equals("PROJ"))
                map.projection = chunk.readUnsignedShort();
            else if (imapTag.equals("VMAP"))
                map.vmapName = chunk.readString();
            else if (imapTag.equals("AXIS"))
                map.axis = chunk.readUnsignedShort();
            else if (imapTag.equals("IMAG"))
                map.clipIndex = chunk.readVX();
            else if (imapTag.equals("WRAP")){
                map.wrapw_type = chunk.readUnsignedShort();
                map.wrapw_type = chunk.readUnsignedShort();
            } else if (imapTag.equals("WRPW")){
                map.wrapw = new FloatParam(chunk.readFloat());
                map.wrapw.envelopeIndex = chunk.readVX();
            } else if (imapTag.equals("WRPH")){
                map.wraph = new FloatParam(chunk.readFloat());
                map.wraph.envelopeIndex = chunk.readVX();
            } else if (imapTag.equals("AAST")){
                map.aas_flags = chunk.readUnsignedShort();
                map.aa_strength = chunk.readFloat();
            } else if (imapTag.equals("PIXB"))
                map.pblend = chunk.readUnsignedShort();
            else if (imapTag.equals("STCK")){
                map.stck = new FloatParam(chunk.readFloat());
                map.stck.envelopeIndex = chunk.readVX();
            } else if (imapTag.equals("TAMP")){
                map.amplitude = new FloatParam(chunk.readFloat());
                map.amplitude.envelopeIndex = chunk.readVX();
            }
            
            if (chunk.getReadFromMark() < imapLen)
                chunk.skipBytes(imapLen-chunk.getReadFromMark());
        }
        return map;
    }
    
    private ProceduralMap readProcMap(ChunkStream chunk) throws IOException
    {
        ProceduralMap map = new ProceduralMap();
        while (chunk.available() > 0) {
            String tag = chunk.readTag();
            int mapLen = chunk.readUnsignedShort();
            int _offset = chunk.available();
            if (tag.equals("TMAP")){
                byte[] b = new byte[mapLen];
                chunk.read(b, 0, mapLen);
                this.readTextureMap(map, b);
            } else if (tag.equals("AXIS"))
                map.axis = chunk.readUnsignedShort();
            else if (tag.equals("VALU")) {
                map.value[0] = chunk.readFloat();
                if (mapLen >= 8)
                    map.value[1] = chunk.readFloat();
                if (mapLen >= 12)
                    map.value[2] = chunk.readFloat();
            } else if (tag.equals("FUNC")) {
                map.name = chunk.readString();
                int dataLen = mapLen - (_offset - chunk.available());
                map.data = new byte[dataLen];
                int fDataRead = chunk.read(map.data, 0, dataLen);
                if (dataLen != fDataRead)
                    logger.warning("EOF ???");
            }
            
            int read = (_offset - chunk.available());
            if (read < mapLen)
                chunk.skipBytes(mapLen - read);
        }
        return map;
    }
   
    private GradientMap readGradientMap(ChunkStream chunk) throws IOException {
        GradientMap map = new GradientMap();
        
        while (chunk.available() > 0) {
            String tag = chunk.readTag();
            int mapLen = chunk.readUnsignedShort();
            int _offset = chunk.available();
            if (tag.equals("TMAP")){
                byte[] b = new byte[mapLen];
                chunk.read(b, 0, mapLen);
                this.readTextureMap(map, b);
            } 
            else if (tag.equals("PNAM"))
                map.paramName = chunk.readString();
             else if (tag.equals("INAM"))
                map.itemName = chunk.readString();
             else if (tag.equals("GRST"))
                map.start = chunk.readFloat();
             else if (tag.equals("GREN"))
                map.end = chunk.readFloat();
             else if (tag.equals("GRPT"))
                map.repeat = chunk.readUnsignedShort();
             else if (tag.equals("FKEY")) {
                 map.keys = new GradientKey[mapLen/20];
                 for (int i = 0; i < (map.keys.length); i++)
                     map.keys[i] = new GradientKey(chunk.readFloat(),
                             chunk.readFloat(),chunk.readFloat(),
                             chunk.readFloat(), chunk.readFloat());
             } else if (tag.equals("IKEY")) {
                 map.interpolationCodes = new GradientInterpolationType[mapLen/2];
                 for (int i = 0; i < (map.keys.length); i++)
                     map.interpolationCodes[i] = 
                             GradientInterpolationType.from(
                             chunk.readUnsignedShort());
             }
            
            int read = (_offset - chunk.available());
            if (read < mapLen)
                chunk.skipBytes(mapLen - read);
        }
        return map;
    }
    
    private void readTextureMap(TextureMap map, byte[] buffer) throws IOException {
        ChunkStream chunk = new ChunkStream("TMAP", buffer);
        while (chunk.available() > 0) {
            String tag = chunk.readTag();
            int size = chunk.readUnsignedShort();
            chunk.setPositionMark();
            if (tag.equals("SIZE")) {
                map.size = new Vector3dParam(chunk.readFloat(),
                        chunk.readFloat(),chunk.readFloat());
                map.size.envelopeIndex = chunk.readVX();
            } else if (tag.equals("CNTR")) {
                map.center = new Vector3dParam(chunk.readFloat(),
                        chunk.readFloat(),chunk.readFloat());
                map.center.envelopeIndex = chunk.readVX();
            } else if (tag.equals("ROTA")) {
                map.rotate = new Vector3dParam(chunk.readFloat(),
                        chunk.readFloat(),chunk.readFloat());
                map.rotate.envelopeIndex = chunk.readVX();
            } else if (tag.equals("FALL")) {
                map.fallType = chunk.readUnsignedShort();
                map.falloff = new Vector3dParam(chunk.readFloat(),
                        chunk.readFloat(),chunk.readFloat());
                map.falloff.envelopeIndex = chunk.readVX();
            } else if (tag.equals("OREF")) {
                map.refObjects = chunk.readString();
            } else if (tag.equals("CSYS")) {
                map.coordSys = chunk.readUnsignedShort();
            }
            
            if (chunk.getReadFromMark() < size)
                chunk.skipBytes(size - chunk.getReadFromMark());
        }
        
    }
    
    
}
